﻿package com.zouloux.core.data
{
	import com.zouloux.core.data.IDataItem;
	import com.zouloux.core.events.DataEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	/**
	 * Les différents events utilisés
	 */
	[Event(name="update", type="com.zouloux.core.events.DataEvent")]
	[Event(name="change", type="com.zouloux.core.events.DataEvent")]
	
	/**
	 * ...
	 * @author ZoulouX
	 */
	public class DataCollection extends EventDispatcher implements IDataCollection
	{
		/**
		 * La collection
		 */
		protected var _collection			:Array 			= [];
		
		/**
		 * L'index courant
		 */
		protected var _index				:uint			= 0;
		
		/**
		 * Parcourir en boucle
		 */
		protected var _loop					:Boolean		= false;
		
		/**
		 * Forcer le dispatch des event. Permet de dispatcher même si les valeurs n'ont pas changées.
		 */
		protected var _forceDispatch		:Boolean		= false;
		
		
		/**
		 * Forcer le dispatch des event. Permet de dispatcher même si les valeurs n'ont pas changées.
		 */
		public function get forceDispatch ():Boolean { return _forceDispatch; }
		public function set forceDispatch (value:Boolean):void 
		{
			_forceDispatch = value;
		}
		
		/**
		 * Récupérer le premier élément
		 */
		public function get first ():IDataItem
		{
			// Vérifier la longueur de la collection
			if (_collection.length > 0)
			{
				// On retourne l'élément concerné
				return _collection[0];
			}
			else throw new Error("Unable to get first element, the collection is void");
		}
		
		/**
		 * Récupérer le dernier élément
		 */
		public function get last ():IDataItem
		{
			// Vérifier la longueur de la collection
			if (_collection.length > 0)
			{
				// On retourne l'élément concerné
				return _collection[_collection.length - 1];
			}
			else throw new Error("Unable to get last element, the collection is void");
		}
		
		/**
		 * Récupérer l'élément courant
		 */
		public function get current ():IDataItem
		{
			// Vérifier la longueur de la collection
			if (_collection.length > 0)
				return _collection[_index];
			else throw new Error("Unable to get current element, the collection is void");
		}
		
		/**
		 * Passer à l'élément précédent
		 */
		public function get prev ():IDataItem
		{
			// Vérifier qu'on soit pas aux limites
			if (_index > 0)
				_index --;
			else if (_loop == true)
				_index = _collection.length - 1;
			
			// Ca à changé
			dispatchUpdate();
			
			// Retourner l'élément
			return current;
		}
		
		/**
		 * Passer à l'élément suivant
		 */
		public function get next ():IDataItem
		{
			// Vérifier qu'on soit pas aux limites
			if (_index < _collection.length - 1)
				_index ++;
			else if (_loop == true)
				_index = 0;
			
			// Ca à changé
			dispatchUpdate();
			
			// Retourner l'élément
			return current;
		}
		
		/**
		 * Récupérer le nombre d'éléments de la collection
		 */
		public function get length ():uint
		{
			return _collection.length;
		}
		
		/**
		 * Récupérer tous les éléments de collection sous forme de tableau.
		 * Attention, le tableau ne doit contenir que des éléments de type IDataItem.
		 */
		public function get all ():Array
		{
			return _collection;
		}
		public function set all (pValue:Array):void
		{
			// Vérifier le type de tous les éléments
			var ok:Boolean = true;
			for each (var item:* in pValue)
			{
				if (!(item is IDataItem))
				{
					ok = false;
					break;
				}
			}
			
			// Si c'est ok
			if (ok)
			{
				// On enregistre la nouvelle collection
				_collection = pValue;
				
				// Vérifier si l'index est toujours bon
				checkIndex();
				
				// Le contenu à changé
				dispatchChange();
			}
			else throw new Error("Array must contain only IDataItem");
		}
		
		/**
		 * Définir l'index
		 */
		public function get index ():uint
		{
			return _index;
		}
		public function set index (pValue:uint):void 
		{
			// Vérifier si la valeur entre dans la collection
			if (pValue < _collection.length)
			{
				// Enregistrer la valeur
				_index = pValue;
			}
			else if (_loop)
			{
				// Remettre à zero si ça loop
				_index = 0;
			}
			else throw new Error("Index out of bounds (" + pValue + " is out of " + (_collection.length - 1) +")");
			
			// Vérifier si la valeur est différente et/ou si on doit dispatcher dans tous les cas
			if (pValue != _index || _forceDispatch)
				dispatchUpdate();
		}
		
		/**
		 * Permet de boucler sur les accesseurs 'next', 'prev' et 'index'
		 */
		public function get loop ():Boolean { return _loop; }
		public function set loop (value:Boolean):void 
		{
			_loop = value;
		}
		
		/**
		 * Savoir si la collection est vide
		 */
		public function get isVoid ():Boolean { return length == 0; }
		
		
		/**
		 * Le constructeur de la collection
		 * @param	pValues : Les données de la collection, ce tableau doit impérativement contenir que des IDataItem
		 * @param	pLoop : Définit le comportement de bouclage de la collection
		 */
		public function DataCollection (pValues:Array = null, pLoop:Boolean = false)
		{
			// Vérifier si des données sont passées en paramètres
			if (pValues != null)
				all = pValues;
			
			// Vérifier s'il faut boucler
			_loop = pLoop;
		}
		
		/**
		 * Récupérer un élément par son index
		 * @param	pIndex : l'index en question
		 * @return : bah, l'élément
		 */
		public function getElement (pIndex:uint):IDataItem
		{
			// Vérifier qu'on soit dans les limites
			if (pIndex < _collection.length)
				return _collection[pIndex];
			else throw new Error("pIndex is out of bounds");
		}
		
		/**
		 * Récupérer l'index d'un élément
		 * @param	pElement : L'élément en question
		 * @return : devine!
		 */
		public function getIndex (pElement:IDataItem):uint
		{
			// Mémoriser le nombre d'éléments
			var total:uint = length;
			
			// Parcourir la collection
			for (var i:uint = 0; i < total; i++)
			{
				// Si on a trouvé l'élément
				if (_collection[i] == pElement)
					return i;
			}
			
			// On a rien trouvé
			throw new Error("pElement not found in collection");
		}
		
		/**
		 * Ajouter un élément à la fin de la collection
		 * @param	pElement : L'élément en question
		 */
		public function add (pElement:IDataItem):void
		{
			// Ajouter à la liste
			_collection.push(pElement);
			
			// Ca a changé
			dispatchChange();
		}
		
		/**
		 * Ajouter un élément à une certaine place
		 * @param	pElement : L'élément en question
		 * @param	pAt : L'index où placer l'élément. Cet index doit être en 0 et lenght
		 */
		public function addAt (pElement:IDataItem, pAt:uint):void
		{
			// Vérifier que l'index entre dans les limites
			if (pAt <= _collection.length)
			{
				// Le nouveau tableau
				var newCollection:Array = [];
				
				// Calculer la longueur après l'ajout
				var total:uint = _collection.length + 1;
				
				// Parcourir la collection
				for (var i:uint = 0; i < total; i++) 
				{
					// Si on tombe sur l'endroit où mettre l'élément
					if (i < pAt)
						newCollection.push(_collection[i]);
					if (i == pAt)
						newCollection.push(pElement);
					else
						newCollection.push(_collection[i - 1]);
				}
				
				// Enregistrer la nouvelle collection
				_collection = newCollection;
			}
			else throw new Error("pAt is out of bounds");
			
			// Ca a changé
			dispatchChange();
		}
		
		/**
		 * Remplir la collection depuis les données d'une remote
		 * /!\ Attention, cette fonction est récursive!
		 * Il faut faire très attention avec les références croisées.
		 * @param	pData : Les données préparées de la remote.
		 * @param	pType : Le type à récupérer au premier niveau des données pour remplir la collection.
		 */
		public function addFromRemote (pData:*, pType:Class):void
		{
			// Vérifier que les données sont "parsables"
			if (typeof(pData) == "object" || typeof(pData) == "array")
			{
				// Parser les données
				for each (var dataItem:* in pData)
				{
					// Récupérer ceux qui sont du type défini, sinon on renvoie dans la moulinette
					if (dataItem is pType && dataItem is IDataItem)
						add(dataItem);
					else
						addFromRemote(dataItem, pType);
				}
			}
		}
		
		/**
		 * Effacer un élément
		 * @param	pElement : l'élément en question
		 */
		public function remove (pElement:IDataItem):void
		{
			// Mesurer la collection
			var total:uint = _collection.length;
			
			// Vérifier si la collection n'est pas vide
			if (total > 0)
			{
				// La nouvelle collection
				var newCollection:Array = [];
				
				// Parcourir la collection
				for (var i:int = 0; i < total; i++) 
				{
					// Ajouter à la nouvelle collection si l'élément courant est différent de l'élément à supprimer
					if (_collection[i] != pElement)
						newCollection.push(_collection[i]);
				}
				
				// Enregistrer la nouvelle collection
				_collection = newCollection;
				
				// Décaler l'index s'il le faut
				checkIndex();
				
				// Ca a changé
				dispatchChange();
			}
			else throw new Error("Unable to delete, the collection is void.");
		}
		
		/**
		 * Effacer un élément à un index précis
		 * @param	pIndex : l'index en question
		 */
		public function removeAt (pIndex:uint):void
		{
			// Mesurer la collection
			var total:uint = _collection.length;
			
			// Vérifier que la collection ne soit pas vide
			if (total > 0)
			{
				// Vérifier si on est tape bien sur un bon index
				if (pIndex < total)
				{
					// La nouvelle collection
					var newCollection:Array = [];
					
					// Trier tous les éléments
					for (var i:uint = 0; i < total; i++)
					{
						// Si ce n'est pas l'index que l'on veut enlever
						// On ajoute dans le nouveau tableau
						if (i != pIndex)
							newCollection.push(_collection[i]);
					}
					
					// Remplacer la collection
					_collection = newCollection;
					
					// Décaler l'index s'il le faut
					checkIndex();
					
					// Ca a changé
					dispatchChange();
				}
				else throw new Error("pIndex is out of bounds.");
			}
			else throw new Error("Unable to delete, the collection is void.");
		}
		
		// Vider la collection
		public function clear ():void
		{
			// On force l'index à 0
			_index = 0;
			
			// Recréer le tableau
			_collection = [];
			
			// Ca a changé
			dispatchChange();
		}
		
		/**
		 * Exécute une fonction sur chaque élément de la collection
		 * @param	pHandler : la fonction
		 */
		public function forEach (pHandler:Function):void
		{
			// Mesurer
			var total:uint = _collection.length;
			
			// Parcourir la collection
			for (var i:int = 0; i < total; i++) 
			{
				// Appeler la fonction pour chaque élément
				// Transmettre l'élément et son index en second paramètre
				pHandler(_collection[i], i);
			}
		}
		
		/**
		 * Vérifier si l'index sort des limites.
		 * Si l'index sort, il est automatiquement corrigé.
		 */
		private function checkIndex ():void
		{
			// Si c'est trop grand
			if (index >= _collection.length)
			{
				// Si on loop, on met à 0, sinon à la fin
				if (_loop)
					index = 0;
				else
					index = _collection.length - 1;
			}
		}
		
		/**
		 * Changement de l'ordre des données
		 */
		protected function dispatchUpdate ():void
		{
			dispatchEvent(new DataEvent(DataEvent.UPDATE));
		}
		
		/**
		 * Changement des données
		 */
		protected function dispatchChange ():void
		{
			dispatchEvent(new DataEvent(DataEvent.CHANGE));
		}
	}
}